package qc.module.qms.dps.consumer;

import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import qc.common.core.constants.QmsConstant;
import qc.common.core.constants.QmsMQConstant;
import qc.common.core.enums.qms.QmsDataFlowOperateTypeEnum;
import qc.common.core.enums.qms.QmsPointDataQualityEnum;
import qc.common.core.enums.qms.QmsPointTimeSegmentEnum;
import qc.common.core.utils.QmsTimeUtil;
import qc.module.qms.algorithm.NumericCalc.dto.QmsNumericCalcPointRelation;
import qc.module.qms.algorithm.NumericCalc.dto.QmsNumericCalcPointTriggerHit;
import qc.module.qms.algorithm.NumericCalc.dto.QmsNumericCalcUseMethod;
import qc.module.qms.dto.calc.QmsPointDataCalcResultDto;
import qc.module.qms.dto.calc.QmsPointDataCalcSaveResultDto;
import qc.module.qms.dto.mq.QmsMqPointDataMessageDto;
import qc.module.qms.dto.pointdata.QmsPointDataManageResultDto;
import qc.module.qms.dto.pointdata.QmsPointNumericDataDto;
import qc.module.qms.entity.QmsPoint;
import qc.module.qms.service.QmsBasicConfigCacheGetService;
import qc.module.qms.service.calc.QmsNumericCalcGetService;
import qc.module.qms.service.pointdata.QmsPointDataCalcService;
import qc.module.qms.service.pointdata.QmsPointDataLatestService;
import qc.module.qms.service.pointdata.QmsPointDataQueryService;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * 点号数据变化触发关联点号计算Consumer
 *
 * @author QuCheng Tech
 * @since 2025/8/12
 */
@Component
@RocketMQMessageListener(
        topic = QmsMQConstant.MQ_POINTDATA_TOPIC,
        consumerGroup = QmsMQConstant.MQ_POINTDATA_CONSUMER_CALC_GROUP
)
public class PointDataTriggerCalcConsumer implements RocketMQListener<QmsMqPointDataMessageDto> {
    private static final Logger logger = LoggerFactory.getLogger(PointDataTriggerCalcConsumer.class);

    @Autowired
    private QmsBasicConfigCacheGetService basicConfigCacheGetService;

    @Autowired
    private QmsNumericCalcGetService numericCalcGetService;

    @Autowired
    private QmsPointDataQueryService pointDataQueryService;

    @Autowired
    private QmsPointDataLatestService pointDataLatestService;

    @Autowired
    private QmsPointDataCalcService pointDataCalcService;

    @Override
    public void onMessage(QmsMqPointDataMessageDto message) {
        if (message != null) {
            try {
                logger.debug("onMessage 点号数据触发点号计算处理，点号[" + message.getPointid() + "]，时段类型[" + message.getTmseg()
                        + "]，是否为人工操作[" + message.isManual() + "]，消息来源操作类型[" + message.getOptype()
                        + "]，操作用户名称[" + message.getUserName() + "]，数据质量[" + message.getQuality()
                        + "]，数据时间段[" + QmsTimeUtil.format(message.getBeginTime(), message.getTmseg())
                        + "]-[" + QmsTimeUtil.format(message.getEndTime(), message.getTmseg())
                        + "]，成功条数[" + message.getAffectCount() + "]，失败条数[" + message.getFailCount() + "]");

                //当前点号数据消息来源的的处理流程节点类型，判断如果来源类型是数据同步来的消息不触发关联点号计算（作为数据同步的数据均使用同步系统来的数据，不需要自己进行计算）
                QmsDataFlowOperateTypeEnum messageFromOptype = message.getOptype();
                if (messageFromOptype == QmsDataFlowOperateTypeEnum.SAVE_POINT_DATA_FROM_DATA_SYNC) {
                    logger.debug("onMessage 点号数据触发点号计算处理，点号[" + message.getPointid() + "]，时段类型[" + message.getTmseg()
                            + "]，是否为人工操作[" + message.isManual() + "]，消息来源操作类型[" + message.getOptype()
                            + "]，点号数据为同步数据，不进行关联点号数值计算");
                } else {
                    String triggerPointid = message.getPointid();
                    QmsPointTimeSegmentEnum triggerPointTmseg = message.getTmseg();
                    LocalDateTime triggerDataBeginTime = message.getBeginTime();
                    LocalDateTime triggerDataEndTime = message.getEndTime();
                    //是否为保存数据（新增或修改数据），区分是点号数据保存还是人工设置数据质量（删除、错误、人工数据）
                    boolean isSaveDatas = true;
                    if (message.isManual() == true) {
                        //只要在人工操作，并且是设置数据质量（删除、错误、人工数据）判定为不是保存数据；
                        QmsPointDataQualityEnum setQuality = message.getQuality();
                        if (setQuality == QmsPointDataQualityEnum.DELETE || setQuality == QmsPointDataQualityEnum.TEST || setQuality == QmsPointDataQualityEnum.MANUAL)
                            isSaveDatas = false;
                    }
                    if (isSaveDatas == true)//如果是保存数据，日志中输出实际受影响的数据条数
                        logger.info("onMessage 点号数据触发数值计算处理，触发点号[" + triggerPointid + "]，时段类型[" + triggerPointTmseg + "]，是否为人工操作[" + message.isManual()
                                + "]，操作类型[" + message.getQuality() + "]，实际受影响数据时段[" + QmsTimeUtil.format(triggerDataBeginTime, triggerPointTmseg) + "]-[" + QmsTimeUtil.format(triggerDataEndTime, triggerPointTmseg)
                                + "]，实际受影响数据共[" + ((message.getAffectDatas() == null || message.getAffectDatas().size() < 0x1) ? "0" : message.getAffectDatas().size()) + "]条");
                    else
                        logger.info("onMessage 点号数据触发数值计算处理，触发点号[" + triggerPointid + "]，时段类型[" + triggerPointTmseg + "]，是否为人工操作[" + message.isManual()
                                + "]，操作类型[" + message.getQuality() + "]，实际受影响数据时段[" + QmsTimeUtil.format(triggerDataBeginTime, triggerPointTmseg) + "]-[" + QmsTimeUtil.format(triggerDataEndTime, triggerPointTmseg) + "]");

                    //点号数据触发关联点号的数值计算处理开始
                    //根据当前点号和数据时间段计算出需要处理的关联点号（集合）和时间段
                    List<QmsNumericCalcPointRelation> calcPointRelations = numericCalcGetService.getPointTriggerRelations(triggerPointid, triggerPointTmseg);
                    if (calcPointRelations == null || calcPointRelations.size() < 0x1) {
                        //没有触发计算的关联点号关系
                        logger.info("onMessage 点号数据触发数值计算处理，触发点号[" + triggerPointid + "]，时段类型[" + triggerPointTmseg + "]，点号触发关联点号关系集合为空");
                    } else {
                        //有触发的计算关联点号关系：根据起止时间段计算触发关联点号计算的时间段
                        //先计算出所有触发关联计算的点号及时间段，然后在根据点号的计算顺序排序后逐个进行计算
                        List<QmsNumericCalcPointTriggerHit> pointTriggerHits = new ArrayList<>();
                        for (QmsNumericCalcPointRelation relation : calcPointRelations) {
                            QmsNumericCalcPointTriggerHit triggerHit = new QmsNumericCalcPointTriggerHit();

                            //设置点号触发关联关系和触发数据的起止时标
                            triggerHit.setRelation(relation);
                            triggerHit.setTriggerDataBeginTime(triggerDataBeginTime);
                            triggerHit.setTriggerDataEndTime(triggerDataEndTime);

                            //根据触发数据的起止时标计算命中的点号计算起止时间段
                            String hitPointid = relation.getPointid();
                            QmsPointTimeSegmentEnum hitPointTmseg = relation.getTmseg();

                            logger.info("onMessage 点号数据触发数值计算处理，触发点号[" + triggerPointid + "]，时段类型[" + triggerPointTmseg
                                    + "]，触发数据时间段[" + QmsTimeUtil.format(triggerDataBeginTime, triggerPointTmseg) + "]-[" + QmsTimeUtil.format(triggerDataEndTime, triggerPointTmseg)
                                    + "]，命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg + "]");

                            /**
                             * 触发命中计算的起始、截止时间处理：
                             * 点号数据触发命中计算的判断只需要判断时段是否已经计算过，因此只需要判断命中点号计算时段是否＜点号最新定时计算时段即可
                             *
                             * 1.使用触发数据的起始、截止时间加上起始、截止时间偏移（如有，加上对应时间偏移）
                             * 2.如果不包含起始时间点，计算出的时段往后一个时段；如果不包含截止时间点，计算出的时段往前一个时段；
                             * 3.起始时间：判断是否要往前取数据，如往前取数据需要查询前一条（的后一条）有效数据的时标作为触发的起始时间
                             * 4.根据处理得到的触发起始时间根据命中点号时段类型计算所在时段时间点
                             * 5.查询点号的最新定时计算时间，如果计算出的时段已经计算则需要进行重新计算，如果计算出的时段没有计算则本次不需要进行计算（等待定时计算即可）
                             */
                            //时间处理步骤 1：
                            LocalDateTime triggerBeginTime = LocalDateTime.of(triggerDataBeginTime.toLocalDate(), triggerDataBeginTime.toLocalTime());
                            LocalDateTime triggerEndTime = LocalDateTime.of(triggerDataEndTime.toLocalDate(), triggerDataEndTime.toLocalTime());
                            if (relation.getTmbeginoffset() != null && relation.getTmbeginoffset().intValue() != 0x0) {
                                //如果查询点号数据的起始时间偏移时段数有效（不为空、不为0），可以为正负（自动取绝对值，只能往前指定正数时段数）
                                int beforeInterval = 0x0 - Math.abs(relation.getTmbeginoffset().intValue());
                                triggerBeginTime = QmsTimeUtil.getIntervalPeriodTime(triggerBeginTime, triggerPointTmseg, beforeInterval);
                            }
                            if (relation.getTmendoffset() != null && relation.getTmendoffset().intValue() != 0x0) {
                                //如果查询点号数据的截止时间偏移时段数有效（不为空、不为0），可以为正负（自动取绝对值，只能往后指定正数时段数）
                                int afterInterval = Math.abs(relation.getTmendoffset().intValue());
                                triggerEndTime = QmsTimeUtil.getIntervalPeriodTime(triggerEndTime, triggerPointTmseg, afterInterval);
                            }
                            //时间处理步骤 2：截止时间
                            //是否包含起始时间点、是否包含截止时间点、是否需要起始时间点前的最新一条数据
                            if (relation.getExcludeendtm() != null && relation.getExcludeendtm().booleanValue() == true)
                                triggerEndTime = QmsTimeUtil.getIntervalPeriodTime(triggerEndTime, triggerPointTmseg, -1);//往前一个时段
                            //时间处理步骤 2：开始时间                
                            //时间处理步骤 3：开始时间
                            if (relation.getNeedbegintmlatest() != null && relation.getNeedbegintmlatest().booleanValue() == true && relation.getBegintmlatestoffset() != null) {
                                //如果要查询起始时间点前的最新一条数据时往前取数据的最大时段数，0或正负数均有效
                                int beginTimeLatestDataInterval = relation.getBegintmlatestoffset().intValue();
                                //为0表示不往前取数据，只取开始时间点的数据;为负值表示不限制往前的时间，取不大于开始时间点的最新一条数据;为正整数表示限制往前的时段数;
                                if (beginTimeLatestDataInterval == 0x0) {
                                    //往前取数据时段数为0，表示只取起始时间点的数据，前面已经赋值为起始时间点，此处不用做任何修改设置
                                } else if (beginTimeLatestDataInterval > 0x0) {
                                    //往前取数据时段数为正整数，表示指定最多往前n个时段，不管实际点号数据情况，始终往前n个时段
                                    triggerBeginTime = QmsTimeUtil.getIntervalPeriodTime(triggerBeginTime, triggerPointTmseg, -beginTimeLatestDataInterval);
                                } else {
                                    //往前取数据时段数为负值，表示不限制往前的时段数；需要查询起始时间点之前最新一条有效数据
                                    QmsPointNumericDataDto noMoreThanData = pointDataQueryService.getLessThan(triggerPointid, triggerPointTmseg, triggerBeginTime, QmsConstant.POINT_DATA_VALID_QUALITY_FROM);
                                    if (noMoreThanData != null)
                                        triggerBeginTime = noMoreThanData.getTm();
                                }
                            } else if (relation.getExcludebegintm() != null && relation.getExcludebegintm().booleanValue() == true) {
                                //如果起始时间不需要往前时再判断是否不包含起始时间点
                                triggerBeginTime = QmsTimeUtil.getNextPeriodTime(triggerBeginTime, triggerPointTmseg);//往后一个时段
                            }

                            //时间处理步骤 4：根据触发数据的起止时间获取触发命中起止时间
                            LocalDateTime hitBeginTime = QmsTimeUtil.getPeriodTime(triggerBeginTime, hitPointTmseg);
                            LocalDateTime hitEndTime = QmsTimeUtil.getPeriodTime(triggerEndTime, hitPointTmseg);

                            //时间处理步骤 5：判断点号定时计算的时间
                            LocalDateTime pointLatestCalcDataTime = pointDataLatestService.getCalcDataTime(hitPointid, hitPointTmseg);
                            if (pointLatestCalcDataTime == null) {
                                //如果最新定时计算的数据时标为空表示没有进行过任何的定时计算，此时不计算触发命中的点号；
                                logger.info("onMessage 点号数据触发数值计算处理，触发点号[" + triggerPointid + "]，时段类型[" + triggerPointTmseg
                                        + "]，触发数据时间段[" + QmsTimeUtil.format(triggerDataBeginTime, triggerPointTmseg) + "]-[" + QmsTimeUtil.format(triggerDataEndTime, triggerPointTmseg)
                                        + "]，命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg + "]，命中计算时间段[" + QmsTimeUtil.format(hitBeginTime, hitPointTmseg)
                                        + "]-[" + QmsTimeUtil.format(hitEndTime, hitPointTmseg) + "]，获取到点号最新定时计算时间为空，等待定时计算，不触发命中点号数值计算");
                            } else {
                                //如果最新定时计算的数据时标＞触发命中计算的起始时间
                                if (pointLatestCalcDataTime.isBefore(hitBeginTime)) {
                                    //最新定时计算的数据时标＜触发命中计算的起始时间；起始和截止时间都在定时计算的时标之前，都不需要计算，等待定时计算即可
                                    logger.info("onMessage 点号数据触发数值计算处理，触发点号[" + triggerPointid + "]，时段类型[" + triggerPointTmseg
                                            + "]，触发数据时间段[" + QmsTimeUtil.format(triggerDataBeginTime, triggerPointTmseg) + "]-[" + QmsTimeUtil.format(triggerDataEndTime, triggerPointTmseg)
                                            + "]，命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg + "]，命中计算时间段[" + QmsTimeUtil.format(hitBeginTime, hitPointTmseg)
                                            + "]-[" + QmsTimeUtil.format(hitEndTime, hitPointTmseg) + "]，获取到点号最新定时计算时间[" + QmsTimeUtil.format(pointLatestCalcDataTime, hitPointTmseg) + "]，等待定时计算，不触发命中点号数值计算");
                                } else {
                                    //最新定时计算的数据时标≥触发命中计算的起始时间
                                    //从触发命中计算的起始时间开始计算，保持计算出的起始时间不变
                                    //最新定时计算的数据时标＜触发命中计算的截止时间；截止时间在定时计算的时标之前，设置截止时间为最新定时计算的数据时标；之后的数据等待定时计算
                                    if (pointLatestCalcDataTime.isBefore(hitEndTime))
                                        hitEndTime = pointLatestCalcDataTime;

                                    logger.info("onMessage 点号数据触发数值计算处理，触发点号[" + triggerPointid + "]，时段类型[" + triggerPointTmseg
                                            + "]，触发数据时间段[" + QmsTimeUtil.format(triggerDataBeginTime, triggerPointTmseg) + "]-[" + QmsTimeUtil.format(triggerDataEndTime, triggerPointTmseg)
                                            + "]，命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg + "]，获取到点号最新定时计算时间[" + QmsTimeUtil.format(pointLatestCalcDataTime, hitPointTmseg)
                                            + "]，触发命中点号数值计算时间段[" + QmsTimeUtil.format(hitBeginTime, hitPointTmseg) + "]-[" + QmsTimeUtil.format(hitEndTime, hitPointTmseg) + "]");

                                    //只有此种情况下需要进行触发命中点号的计算
                                    triggerHit.setHitDataBeginTime(hitBeginTime);
                                    triggerHit.setHitDataEndTime(hitEndTime);

                                    pointTriggerHits.add(triggerHit);
                                }
                            }
                        }

                        //指定optype为点号触发命中点号计算
                        QmsDataFlowOperateTypeEnum optype = QmsDataFlowOperateTypeEnum.CALC_POINT_DATA_BY_DATA_TRIGGER;

                        //遍历逐个点号数据触发命中信息进行数值计算
                        for (QmsNumericCalcPointTriggerHit triggerHit : pointTriggerHits) {
                            String hitPointid = triggerHit.getRelation().getPointid();
                            QmsPointTimeSegmentEnum hitPointTmseg = triggerHit.getRelation().getTmseg();
                            LocalDateTime hitBeginTime = triggerHit.getHitDataBeginTime();
                            LocalDateTime hitEndTime = triggerHit.getHitDataEndTime();

                            QmsPoint hitPoint = basicConfigCacheGetService.getPoint(hitPointid);
                            if (hitPoint == null) {
                                logger.debug("onMessage 点号数据触发数值计算处理，计算触发命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg + "]，根据命中点号获取不到点号对象，不进行触发命中点号数值计算");
                            } else {
                                QmsNumericCalcUseMethod calcMethod = numericCalcGetService.getPointValidCalcUseMethod(hitPoint, hitPointTmseg);
                                if (calcMethod == null) {
                                    logger.debug("onMessage 点号数据触发数值计算处理，计算触发命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg + "]，无有效的计算方法，不进行触发命中点号数值计算");
                                } else {
                                    QmsPointDataCalcSaveResultDto calcSaveResultDto = pointDataCalcService.calcPointDatas(hitPoint, hitPointTmseg, calcMethod, hitBeginTime, hitEndTime
                                            , optype, false, message.getFlowid(), message.getFromnodeid());
                                    //在pointDataCalcService.calc方法中已经完成日志记录、流程日志消息发送、原始数据处理标记修改
                                    //此处可以不对处理结果进行判断和输出显示
                                    if (calcSaveResultDto == null) {
                                        //计算和保存结果为null
                                        logger.warn("onMessage 点号数据触发数值计算处理，计算触发命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg
                                                + "]，命中计算时间段[" + QmsTimeUtil.format(hitBeginTime, hitPointTmseg) + "]-[" + QmsTimeUtil.format(hitEndTime, hitPointTmseg)
                                                + "]，点号计算和保存的结果对象为null");
                                    } else {
                                        if (calcSaveResultDto.getCalcResult() == null) {
                                            //计算结果为null
                                            logger.warn("onMessage 点号数据触发数值计算处理，计算触发命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg
                                                    + "]，命中计算时间段[" + QmsTimeUtil.format(hitBeginTime, hitPointTmseg) + "]-[" + QmsTimeUtil.format(hitEndTime, hitPointTmseg)
                                                    + "]，点号计算结果对象为null");
                                        } else {
                                            QmsPointDataCalcResultDto calcResult = calcSaveResultDto.getCalcResult();
                                            if (calcResult.isSuccess() == false) {
                                                //计算失败
                                                logger.warn("onMessage 点号数据触发数值计算处理，计算触发命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg
                                                        + "]，命中计算时间段[" + QmsTimeUtil.format(hitBeginTime, hitPointTmseg) + "]-[" + QmsTimeUtil.format(hitEndTime, hitPointTmseg)
                                                        + "]，点号计算结果失败，" + calcResult.getMsg());
                                            } else {
                                                //计算成功
                                                logger.info("onMessage 点号数据触发数值计算处理，计算触发命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg
                                                        + "]，命中计算时间段[" + QmsTimeUtil.format(hitBeginTime, hitPointTmseg) + "]-[" + QmsTimeUtil.format(hitEndTime, hitPointTmseg)
                                                        + "]，点号计算成功，应有数量[" + calcResult.getDueCount() + "]，实有数量[" + calcResult.getActualCount() + "]，计算出的点号数据条数[" + calcResult.getDatas().size() + "]");
                                                if (calcSaveResultDto.getSaveResult() == null) {
                                                    //计算出的点号保存结果为null
                                                    logger.warn("onMessage 点号数据触发数值计算处理，计算触发命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg
                                                            + "]，命中计算时间段[" + QmsTimeUtil.format(hitBeginTime, hitPointTmseg) + "]-[" + QmsTimeUtil.format(hitEndTime, hitPointTmseg)
                                                            + "]，点号计算保存结果对象为null");
                                                } else {
                                                    QmsPointDataManageResultDto saveResult = calcSaveResultDto.getSaveResult();
                                                    if (saveResult.isSuccess() == false) {
                                                        //保存失败
                                                        logger.warn("onMessage 点号数据触发数值计算处理，计算触发命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg
                                                                + "]，命中计算时间段[" + QmsTimeUtil.format(hitBeginTime, hitPointTmseg) + "]-[" + QmsTimeUtil.format(hitEndTime, hitPointTmseg)
                                                                + "]，点号计算结果保存失败，" + saveResult.getMsg());
                                                    } else {
                                                        logger.info("onMessage 点号数据触发数值计算处理，计算触发命中点号[" + hitPointid + "]，时段类型[" + hitPointTmseg
                                                                + "]，命中计算时间段[" + QmsTimeUtil.format(hitBeginTime, hitPointTmseg) + "]-[" + QmsTimeUtil.format(hitEndTime, hitPointTmseg)
                                                                + "]，点号计算结果保存成功，受影响条数[" + saveResult.getAffectCount() + "]");
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (Exception ex) {
                logger.error("根据MQ中的点号数据进行触发点号处理异常，" + ex.getMessage());
            }
        }
    }
}
